package com.hyt.it.ogt.kq.service.gov.aspect;

import cn.hutool.core.util.ReflectUtil;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.it.ogt.kq.common.enums.ProgressEnum;
import com.hyt.it.ogt.kq.common.gov.annotation.Progress;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.utils.CacheKeyManager;
import com.hyt.loginfo.service.IBaseLoginService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static com.hyt.it.ogt.kq.common.gov.enums.CacheKeyEnum.*;

/**
 * <p>
 * 进度切面
 * </p>
 *
 * @author huangyh
 * @since 2021-12-23
 */
@Slf4j
@Aspect
@Component
public class ProgressAspect {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private IBaseLoginService iBaseLoginService;

    /**
     * 切点
     */
    @Pointcut("@annotation(com.hyt.it.ogt.kq.common.gov.annotation.Progress)")
    public void progressPointCut() {

    }

    /**
     * 环绕
     * @param joinPoint 连接点
     * @return 结果
     */
    @Around(value = "progressPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取请求参数
        String taskId = this.getTaskId(joinPoint);
        ProgressEnum progressEnum = this.getAnnotation(joinPoint);
        Object result;
        switch (ProgressEnum.getByValue(progressEnum)) {
            //生成准考证
            case CREATE_ADMISSION_TICKET:
                result = this.createAdmissionTicket(taskId, joinPoint);
                break;
            //导出准考证
            case EXPORT_ADMISSION_TICKET:
                result = this.exportAdmissionTicket(taskId, joinPoint);
                break;
            //生成考务包
            case CREATE_ROOM_DATA:
                result = this.createRoomData(taskId, joinPoint);
                break;
            //生成模拟测试考务包
            case CREATE_MOCK_ROOM_DATA:
                result = this.createMockRoomData(taskId, joinPoint);
                break;
            //默认方法注解值，什么也不做
            default:
                result = joinPoint.proceed();
                break;
        }
        //判断是否执行异常
        if (!ResponseCode.SUCCESS.getCode()
                .equals(((ApiResponse<?>) result).getCode())) {
            //进度异常时执行的操作
            this.whenException(progressEnum, taskId);
        }
        //返回结果
        return result;
    }

    /**
     * 进度异常时执行的操作
     * @param progressEnum 进度注解值
     * @param taskId 考试任务id
     */
    private void whenException(ProgressEnum progressEnum, String taskId) {
        //当且仅当执行异常时才清除分布式锁，防止下次执行进度时锁死无法执行
        //若进度正常执行，则需要手动在进度执行结束后清除分布式锁
        switch (ProgressEnum.getByValue(progressEnum)) {
            //生成考务包
            case CREATE_ROOM_DATA:
                this.cleanCreateRoomDataLock(taskId);
                break;
            //生成模拟测试考务包
            case CREATE_MOCK_ROOM_DATA:
                this.cleanCreateMockRoomDataLock(taskId);
                break;
            //默认方法注解值，什么也不做
            default:
                break;
        }
    }

    /**
     * 生成准考证
     * @param taskId 考试任务id
     * @param joinPoint 连接点
     * @return 结果
     * @throws Throwable 异常
     */
    private Object createAdmissionTicket(String taskId, ProceedingJoinPoint joinPoint) throws Throwable {
        String creatingKey = CREATE_ADMISSION_TICKET_KEY.getCacheEnumKey(taskId);
        String progressKey = CREATE_ADMISSION_TICKET_PROGRESS_KEY.getCacheEnumKey(taskId);
        return this.checkLock(joinPoint, creatingKey, progressKey, "当前任务正在生成准考证...", CREATE_ADMISSION_TICKET_KEY.getExpireSeconds());
    }

    /**
     * 导出准考证
     * @param taskId 考试任务id
     * @param joinPoint 连接点
     * @return 结果
     * @throws Throwable 异常
     */
    private Object exportAdmissionTicket(String taskId, ProceedingJoinPoint joinPoint) throws Throwable {
        String userId = iBaseLoginService.getUserId();
        String creatingKey = EXPORT_ADMISSION_TICKET_KEY.getCacheEnumKey(taskId, userId);
        String progressKey = EXPORT_ADMISSION_TICKET_PROGRESS_KEY.getCacheEnumKey(taskId, userId);
        return this.checkLock(joinPoint, creatingKey, progressKey, "当前用户正在导出准考证...", EXPORT_ADMISSION_TICKET_KEY.getExpireSeconds());
    }

    /**
     * 生成考务包
     * @param taskId 考试任务id
     * @param joinPoint 连接点
     * @return 结果
     * @throws Throwable 异常
     */
    private Object createRoomData(String taskId, ProceedingJoinPoint joinPoint) throws Throwable {
        String creatingKey = CREATE_ROOM_DATA_KEY.getCacheEnumKey(taskId);
        String progressKey = CREATE_ROOM_DATA_PROGRESS_KEY.getCacheEnumKey(taskId);
        return this.checkLock(joinPoint, creatingKey, progressKey, "当前任务正在生成考务包...", CREATE_ROOM_DATA_KEY.getExpireSeconds());
    }

    /**
     * 清除生成考务包分布式锁
     * @param taskId 考试任务id
     */
    private void cleanCreateRoomDataLock(String taskId) {
        String creatingKey = CREATE_ROOM_DATA_KEY.getCacheEnumKey(taskId);
        this.cleanLock(creatingKey);
    }

    /**
     * 成成模拟测试考务包
     * @param taskId 考试任务id
     * @param joinPoint 连接点
     * @return 结果
     * @throws Throwable 异常
     */
    private Object createMockRoomData(String taskId, ProceedingJoinPoint joinPoint) throws Throwable {
        String creatingKey = CREATE_MOCK_ROOM_DATA_KEY.getCacheEnumKey(taskId);
        String progressKey = CREATE_MOCK_ROOM_DATA_PROGRESS_KEY.getCacheEnumKey(taskId);
        return this.checkLock(joinPoint, creatingKey, progressKey, "当前任务正在生成模拟测试考务包...", CREATE_MOCK_ROOM_DATA_KEY.getExpireSeconds());
    }

    /**
     * 清除生成模拟测试考务包分布式锁
     * @param taskId 考试任务id
     */
    private void cleanCreateMockRoomDataLock(String taskId) {
        String creatingKey = CREATE_MOCK_ROOM_DATA_KEY.getCacheEnumKey(taskId);
        this.cleanLock(creatingKey);
    }

    /**
     * 获取考试任务id参数
     * @param joinPoint 连接点
     * @return 考试任务id
     */
    private String getTaskId(JoinPoint joinPoint) {
        //将请求参数转为Map，String是参数名，Object是参数值
        Map<String, Object> param = new HashMap<>();
        Object[] paramValues = joinPoint.getArgs();
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        for (int i = 0; i < paramNames.length; i++) {
            param.put(paramNames[i], paramValues[i]);
        }
        //遍历请求参数Map，获取考试任务id
        String taskId = null;
        for (Map.Entry<?, ?> entry : param.entrySet()) {
            if (String.class.equals(entry.getValue().getClass())
                    && "taskId".equals(entry.getKey())) {
                //参数为字符串类型，且参数名为taskId，即判断为考试任务id，并跳出遍历
                taskId = (String) entry.getValue();
                break;
            }
            //若参数为非字符串类型，则使用反射获取参数对象的考试任务id
            Method getTaskIdMethod = ReflectUtil.getMethodOfObj(entry.getValue(),
                    "getTaskId");
            if (getTaskIdMethod == null) {
                //参数对象没有getTaskId方法，继续遍历下一个参数
                continue;
            }
            //参数对象有getTaskId方法，则直接调用getTaskId方法获取考试任务id，并跳出遍历
            taskId = ReflectUtil.invoke(entry.getValue(), getTaskIdMethod);
            break;
        }
        log.info("# 获取考试任务id参数: {}", taskId);
        return taskId;
    }

    /**
     * 获取方法注解值
     * @param joinPoint 连接点
     * @return 方法注解值
     */
    private ProgressEnum getAnnotation(JoinPoint joinPoint) {
        //获取访问的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取注解参数
        Progress annotation = method.getAnnotation(Progress.class);
        return annotation.name();
    }

    /**
     * 推送编排数据
     * @param taskId 考试任务id
     * @param joinPoint 连接点
     * @return 结果
     * @throws Throwable 异常
     */
    private Object createDataLockTicket(String taskId, ProceedingJoinPoint joinPoint) throws Throwable {
        String creatingKey = DATA_LOCK_KEY.getCacheEnumKey(taskId); //CacheKeyManager.getDataLockKey(taskId);
        String progressKey = DATA_LOCK_PROGRESS_KEY.getCacheEnumKey(taskId); //CacheKeyManager.getDataLockProgressKey(taskId);
        return this.checkLock(joinPoint, creatingKey, progressKey, "当前任务正在处理锁定/取消锁定...",DATA_LOCK_PROGRESS_KEY.getExpireSeconds());
    }


    /**
     * 推送编排数据
     * @param taskId 考试任务id
     * @param joinPoint 连接点
     * @return 结果
     * @throws Throwable 异常
     */
    private Object createKwBingDataLockTicket(String taskId, ProceedingJoinPoint joinPoint) throws Throwable {
        String creatingKey = CacheKeyManager.getKwBingDataLockKey(taskId);
        String progressKey = CacheKeyManager.getBingingLockProgressKey(taskId);
        return this.checkLock(joinPoint, creatingKey, progressKey, "当前任务正在绑定报名任务同步数据...",24 * 60* 60L);
    }

    /**
     * 推送编排数据
     * @param joinPoint 连接点
     * @param creatingKey key
     * @param progressKey 过程key
     * @param message 消息
     * @param expireSeconds 过期时间
     * @return 返回结果
     */
    private Object checkLock(ProceedingJoinPoint joinPoint, String creatingKey,
                             String progressKey, String message, long expireSeconds) throws Throwable {
        //校验key是否存在
        Boolean creatingFlag = redisTemplate.opsForValue().setIfAbsent(creatingKey,
                true, expireSeconds, TimeUnit.SECONDS);
        Optional<Boolean> optional = Optional.ofNullable(creatingFlag);
        if (optional.isPresent()) {
            if (!optional.get()) {
                //key存在，说明事务正在执行中，则中断进度
                log.error("进度切面中断，原因: {}", message);
                return ApiResponse.builder()
                        .code(ResponseCode.SUCCESS.getCode())
                        .data(message)
                        .build();
            }
        } else {
            //其他原因导致中断
            message = "未知原因";
            log.error("进度切面中断，原因: {}", message);
            return ApiResponse.builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(message)
                    .build();
        }
        //key不存在，说明未在执行中，重置上次进度
        redisTemplate.delete(progressKey);
        //开始执行进度
        return joinPoint.proceed();
    }

    /**
     * 清除分布式锁
     * @param creatingKey 分布式锁key
     */
    private void cleanLock(String creatingKey) {
        Boolean success = redisTemplate.delete(creatingKey);
        success = Optional.ofNullable(success).orElse(false);
        if (!success) {
            log.error("切面中清除分布式锁失败，key值: {}", creatingKey);
        }
    }

}
